package main

import (
	"encoding/json"
	"errors"

	"github.com/grafana/grafana-foundation-sdk/go/cog"
	"github.com/grafana/grafana-foundation-sdk/go/cog/variants"
	"github.com/grafana/grafana-foundation-sdk/go/dashboard"
)

type CustomPanelOptions struct {
	MakeBeautiful bool `json:"makeBeautiful"`
}

func CustomPanelVariantConfig() variants.PanelcfgConfig {
	return variants.PanelcfgConfig{
		Identifier: "custom-panel", // plugin ID
		OptionsUnmarshaler: func(raw []byte) (any, error) {
			options := &CustomPanelOptions{}

			if err := json.Unmarshal(raw, options); err != nil {
				return nil, err
			}

			return options, nil
		},
	}
}

// Compile-time check to ensure that CustomPanelBuilder indeed is
// a builder for a dashboard.Panel.
var _ cog.Builder[dashboard.Panel] = (*CustomPanelBuilder)(nil)

type CustomPanelBuilder struct {
	internal *dashboard.Panel
	errors   map[string]cog.BuildErrors
}

func NewCustomPanelBuilder() *CustomPanelBuilder {
	builder := &CustomPanelBuilder{
		internal: &dashboard.Panel{
			Type: "custom-panel",
		},
		errors: make(map[string]cog.BuildErrors),
	}

	builder.Transparent(false)
	builder.Height(9)
	builder.Span(12)

	return builder
}

func (builder *CustomPanelBuilder) Build() (dashboard.Panel, error) {
	var errs cog.BuildErrors

	for _, err := range builder.errors {
		errs = append(errs, cog.MakeBuildErrors("CustomPanel", err)...)
	}

	if len(errs) != 0 {
		return dashboard.Panel{}, errs
	}

	return *builder.internal, nil
}

func (builder *CustomPanelBuilder) MakeBeautiful() *CustomPanelBuilder {
	if builder.internal.Options == nil {
		builder.internal.Options = &CustomPanelOptions{}
	}
	builder.internal.Options.(*CustomPanelOptions).MakeBeautiful = true

	return builder
}

// Options common to all panels

// Unique identifier of the panel. Generated by Grafana when creating a new panel. It must be unique within a dashboard, but not globally.
func (builder *CustomPanelBuilder) Id(id uint32) *CustomPanelBuilder {
	builder.internal.Id = &id

	return builder
}

// Depends on the panel plugin. See the plugin documentation for details.
func (builder *CustomPanelBuilder) WithTarget(targets cog.Builder[variants.Dataquery]) *CustomPanelBuilder {
	targetsResource, err := targets.Build()
	if err != nil {
		builder.errors["targets"] = err.(cog.BuildErrors)
		return builder
	}
	builder.internal.Targets = append(builder.internal.Targets, targetsResource)

	return builder
}

// Panel title.
func (builder *CustomPanelBuilder) Title(title string) *CustomPanelBuilder {
	builder.internal.Title = &title

	return builder
}

// Panel description.
func (builder *CustomPanelBuilder) Description(description string) *CustomPanelBuilder {
	builder.internal.Description = &description

	return builder
}

// Whether to display the panel without a background.
func (builder *CustomPanelBuilder) Transparent(transparent bool) *CustomPanelBuilder {
	builder.internal.Transparent = &transparent

	return builder
}

// The datasource used in all targets.
func (builder *CustomPanelBuilder) Datasource(datasource dashboard.DataSourceRef) *CustomPanelBuilder {
	builder.internal.Datasource = &datasource

	return builder
}

// Panel height. The height is the number of rows from the top edge of the panel.
func (builder *CustomPanelBuilder) Height(h uint32) *CustomPanelBuilder {
	if !(h > 0) {
		builder.errors["h"] = cog.MakeBuildErrors("h", errors.New("h must be > 0"))
		return builder
	}
	if builder.internal.GridPos == nil {
		builder.internal.GridPos = &dashboard.GridPos{}
	}
	builder.internal.GridPos.H = h

	return builder
}

// Panel width. The width is the number of columns from the left edge of the panel.
func (builder *CustomPanelBuilder) Span(w uint32) *CustomPanelBuilder {
	if !(w > 0) {
		builder.errors["w"] = cog.MakeBuildErrors("w", errors.New("w must be > 0"))
		return builder
	}
	if !(w <= 24) {
		builder.errors["w"] = cog.MakeBuildErrors("w", errors.New("w must be <= 24"))
		return builder
	}
	if builder.internal.GridPos == nil {
		builder.internal.GridPos = &dashboard.GridPos{}
	}
	builder.internal.GridPos.W = w

	return builder
}

// Panel links.
func (builder *CustomPanelBuilder) Links(links []cog.Builder[dashboard.DashboardLink]) *CustomPanelBuilder {
	linksResources := make([]dashboard.DashboardLink, 0, len(links))
	for _, r1 := range links {
		linksDepth1, err := r1.Build()
		if err != nil {
			builder.errors["links"] = err.(cog.BuildErrors)
			return builder
		}
		linksResources = append(linksResources, linksDepth1)
	}
	builder.internal.Links = linksResources

	return builder
}

// Name of template variable to repeat for.
func (builder *CustomPanelBuilder) Repeat(repeat string) *CustomPanelBuilder {
	builder.internal.Repeat = &repeat

	return builder
}

// Direction to repeat in if 'repeat' is set.
// `h` for horizontal, `v` for vertical.
func (builder *CustomPanelBuilder) RepeatDirection(repeatDirection dashboard.PanelRepeatDirection) *CustomPanelBuilder {
	builder.internal.RepeatDirection = &repeatDirection

	return builder
}

// Option for repeated panels that controls max items per row
// Only relevant for horizontally repeated panels
func (builder *CustomPanelBuilder) MaxPerRow(maxPerRow float64) *CustomPanelBuilder {
	builder.internal.MaxPerRow = &maxPerRow

	return builder
}

// The maximum number of data points that the panel queries are retrieving.
func (builder *CustomPanelBuilder) MaxDataPoints(maxDataPoints float64) *CustomPanelBuilder {
	builder.internal.MaxDataPoints = &maxDataPoints

	return builder
}

// List of transformations that are applied to the panel data before rendering.
// When there are multiple transformations, Grafana applies them in the order they are listed.
// Each transformation creates a result set that then passes on to the next transformation in the processing pipeline.
func (builder *CustomPanelBuilder) WithTransformation(transformations dashboard.DataTransformerConfig) *CustomPanelBuilder {
	builder.internal.Transformations = append(builder.internal.Transformations, transformations)

	return builder
}

// The min time interval setting defines a lower limit for the $__interval and $__interval_ms variables.
// This value must be formatted as a number followed by a valid time
// identifier like: "40s", "3d", etc.
// See: https://grafana.com/docs/grafana/latest/panels-visualizations/query-transform-data/#query-options
func (builder *CustomPanelBuilder) Interval(interval string) *CustomPanelBuilder {
	builder.internal.Interval = &interval

	return builder
}

// Overrides the relative time range for individual panels,
// which causes them to be different than what is selected in
// the dashboard time picker in the top-right corner of the dashboard. You can use this to show metrics from different
// time periods or days on the same dashboard.
// The value is formatted as time operation like: `now-5m` (Last 5 minutes), `now/d` (the day so far),
// `now-5d/d`(Last 5 days), `now/w` (This week so far), `now-2y/y` (Last 2 years).
// Note: Panel time overrides have no effect when the dashboard’s time range is absolute.
// See: https://grafana.com/docs/grafana/latest/panels-visualizations/query-transform-data/#query-options
func (builder *CustomPanelBuilder) TimeFrom(timeFrom string) *CustomPanelBuilder {
	builder.internal.TimeFrom = &timeFrom

	return builder
}

// Overrides the time range for individual panels by shifting its start and end relative to the time picker.
// For example, you can shift the time range for the panel to be two hours earlier than the dashboard time picker setting `2h`.
// Note: Panel time overrides have no effect when the dashboard’s time range is absolute.
// See: https://grafana.com/docs/grafana/latest/panels-visualizations/query-transform-data/#query-options
func (builder *CustomPanelBuilder) TimeShift(timeShift string) *CustomPanelBuilder {
	builder.internal.TimeShift = &timeShift

	return builder
}

// Controls if the timeFrom or timeShift overrides are shown in the panel header
func (builder *CustomPanelBuilder) HideTimeOverride(hideTimeOverride bool) *CustomPanelBuilder {
	builder.internal.HideTimeOverride = &hideTimeOverride

	return builder
}

// Dynamically load the panel
func (builder *CustomPanelBuilder) LibraryPanel(libraryPanel dashboard.LibraryPanelRef) *CustomPanelBuilder {
	builder.internal.LibraryPanel = &libraryPanel

	return builder
}

// Sets panel queries cache timeout.
func (builder *CustomPanelBuilder) CacheTimeout(cacheTimeout string) *CustomPanelBuilder {
	builder.internal.CacheTimeout = &cacheTimeout

	return builder
}

// Overrides the data source configured time-to-live for a query cache item in milliseconds
func (builder *CustomPanelBuilder) QueryCachingTTL(queryCachingTTL float64) *CustomPanelBuilder {
	builder.internal.QueryCachingTTL = &queryCachingTTL

	return builder
}

// The display value for this field.  This supports template variables blank is auto
func (builder *CustomPanelBuilder) DisplayName(displayName string) *CustomPanelBuilder {
	if builder.internal.FieldConfig == nil {
		builder.internal.FieldConfig = &dashboard.FieldConfigSource{}
	}
	builder.internal.FieldConfig.Defaults.DisplayName = &displayName

	return builder
}

// Unit a field should use. The unit you select is applied to all fields except time.
// You can use the units ID availables in Grafana or a custom unit.
// Available units in Grafana: https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/valueFormats/categories.ts
// As custom unit, you can use the following formats:
// `suffix:<suffix>` for custom unit that should go after value.
// `prefix:<prefix>` for custom unit that should go before value.
// `time:<format>` For custom date time formats type for example `time:YYYY-MM-DD`.
// `si:<base scale><unit characters>` for custom SI units. For example: `si: mF`. This one is a bit more advanced as you can specify both a unit and the source data scale. So if your source data is represented as milli (thousands of) something prefix the unit with that SI scale character.
// `count:<unit>` for a custom count unit.
// `currency:<unit>` for custom a currency unit.
func (builder *CustomPanelBuilder) Unit(unit string) *CustomPanelBuilder {
	if builder.internal.FieldConfig == nil {
		builder.internal.FieldConfig = &dashboard.FieldConfigSource{}
	}
	builder.internal.FieldConfig.Defaults.Unit = &unit

	return builder
}

// Specify the number of decimals Grafana includes in the rendered value.
// If you leave this field blank, Grafana automatically truncates the number of decimals based on the value.
// For example 1.1234 will display as 1.12 and 100.456 will display as 100.
// To display all decimals, set the unit to `String`.
func (builder *CustomPanelBuilder) Decimals(decimals float64) *CustomPanelBuilder {
	if builder.internal.FieldConfig == nil {
		builder.internal.FieldConfig = &dashboard.FieldConfigSource{}
	}
	builder.internal.FieldConfig.Defaults.Decimals = &decimals

	return builder
}

// The minimum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.
func (builder *CustomPanelBuilder) Min(min float64) *CustomPanelBuilder {
	if builder.internal.FieldConfig == nil {
		builder.internal.FieldConfig = &dashboard.FieldConfigSource{}
	}
	builder.internal.FieldConfig.Defaults.Min = &min

	return builder
}

// The maximum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.
func (builder *CustomPanelBuilder) Max(max float64) *CustomPanelBuilder {
	if builder.internal.FieldConfig == nil {
		builder.internal.FieldConfig = &dashboard.FieldConfigSource{}
	}
	builder.internal.FieldConfig.Defaults.Max = &max

	return builder
}

// Convert input values into a display string
func (builder *CustomPanelBuilder) Mappings(mappings []dashboard.ValueMapping) *CustomPanelBuilder {
	if builder.internal.FieldConfig == nil {
		builder.internal.FieldConfig = &dashboard.FieldConfigSource{}
	}
	builder.internal.FieldConfig.Defaults.Mappings = mappings

	return builder
}

// Map numeric values to states
func (builder *CustomPanelBuilder) Thresholds(thresholds cog.Builder[dashboard.ThresholdsConfig]) *CustomPanelBuilder {
	if builder.internal.FieldConfig == nil {
		builder.internal.FieldConfig = &dashboard.FieldConfigSource{}
	}
	thresholdsResource, err := thresholds.Build()
	if err != nil {
		builder.errors["fieldConfig.defaults.thresholds"] = err.(cog.BuildErrors)
		return builder
	}
	builder.internal.FieldConfig.Defaults.Thresholds = &thresholdsResource

	return builder
}

// Alternative to empty string
func (builder *CustomPanelBuilder) NoValue(noValue string) *CustomPanelBuilder {
	if builder.internal.FieldConfig == nil {
		builder.internal.FieldConfig = &dashboard.FieldConfigSource{}
	}
	builder.internal.FieldConfig.Defaults.NoValue = &noValue

	return builder
}

// Overrides are the options applied to specific fields overriding the defaults.
func (builder *CustomPanelBuilder) WithOverride(matcher dashboard.MatcherConfig, properties []dashboard.DynamicConfigValue) *CustomPanelBuilder {
	if builder.internal.FieldConfig == nil {
		builder.internal.FieldConfig = &dashboard.FieldConfigSource{}
	}
	builder.internal.FieldConfig.Overrides = append(builder.internal.FieldConfig.Overrides, struct {
		Matcher    dashboard.MatcherConfig        `json:"matcher"`
		Properties []dashboard.DynamicConfigValue `json:"properties"`
	}{
		Matcher:    matcher,
		Properties: properties,
	})

	return builder
}
